﻿/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://git.oschina.net/cblock/embedme
 * Copyright 2014~2017 @ ShenZhen ,China
*******************************************************************************/
#include "ComPort.h"
#include "Tracer.h"
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


/**
 *  \brief  构造函数
 *  \param  none
 *  \return none
 *  \note   默认串口波特率为9600,8位数据位,1位停止位,无奇偶校验位,无流控
 */
ComPort::ComPort():
m_errorCount(0)
{
	/* 串口属性默认值 */
	m_attr.m_bandrate 	= BAUD_9600;        /* 波特率为9600 */
	m_attr.m_databits 	= DATA_8;		    /* 8位数据位 */
	m_attr.m_stopbits 	= STOP_1;		    /* 1位停止位 */
	m_attr.m_parity 	= PARITY_NONE;	    /* 无奇偶校验位 */
	m_attr.m_flowctrl	= FLOWCTRL_NONE;    /* 无流控 */
}
/**
 *  \brief  析构函数
 *  \param  none
 *  \return none
 *  \note   none
 */
ComPort::~ComPort()
{
	close();
}

/**
 *  \brief  打开串口
 *  \param  devName 设备名称,如/dev/ttySAC0
 *  \param  mode 设备属性,如IO_MODE_RD_ONLY
 *  \return 成功返回true,失败返回false
 *  \note   如果要修改串口属性,必须在open前进行设置
 */
bool ComPort::open(const char * devName, int ioMode)
{
	bool ret = IODevice::open(devName, ioMode);
	if (!ret)
	{
		return false;
	}
	
	struct termios options; 
	tcgetattr(m_fd, &options);

	/* 设置波特率 */
	uint32 baud;
	switch(m_attr.m_bandrate)
	{
		case BAUD_1200:	baud = B1200; break;
		case BAUD_2400:	baud = B2400; break;
		case BAUD_4800:	baud = B4800; break;
		case BAUD_9600:	baud = B9600; break;
		case BAUD_19200:	baud = B19200;break;
		case BAUD_38400: 	baud = B38400;break;
		case BAUD_57600: 	baud = B57600;break;
		case BAUD_115200:	baud = B115200;break;
		case BAUD_460800: 	baud = B460800;break;
		default:	break;
	}
	cfsetispeed (&options, baud);	 
	cfsetospeed (&options, baud);	 

	/* input modes */
	options.c_iflag = 0;	 

	/* output modes */
	options.c_oflag &= ~OPOST;	

	/* control modes */
	options.c_cflag |= CREAD;     			/* 使能接收器 */

	options.c_cflag &= ~CSIZE;    
	switch(m_attr.m_databits)
	{
		case DATA_5:
			options.c_cflag |= CS5;
			break;
		case DATA_6:
			options.c_cflag |= CS6;
			break;
		case DATA_7:
			options.c_cflag |= CS7;
			break;
		default:
			options.c_cflag |= CS8;			/* 默认8位数据位 */
			break;
	}

	switch(m_attr.m_stopbits)
	{
		case STOP_2:
			options.c_cflag |= CSTOPB;   	/* 2位停止位*/
			break;
		default:
			options.c_cflag &= ~CSTOPB;  	/* 默认1位停止位*/
			break;
	}
		 
	switch(m_attr.m_parity)
	{
		case PARITY_ODD:
			options.c_cflag |= (PARENB|PARODD); /* 奇校验 */
			break;
		case PARITY_EVEN:
			options.c_cflag &= ~PARENB;    		/* 偶校验 */
			options.c_cflag &= ~PARODD;
			break;
		case PARITY_SPACE:
			options.c_cflag &= ~PARENB;    		/* 空格校验 */
			options.c_cflag &= ~CSTOPB;  
			break;
		default:
			options.c_cflag &= ~PARENB;    		/* 默认无校验 */
			break;
	}
	
	switch(m_attr.m_flowctrl)
	{
		case FLOWCTRL_HW:
			options.c_cflag |= CRTSCTS;
			break;
		case FLOWCTRL_SW:
			options.c_cflag |= IXON | IXOFF | IXANY;
			break;
		default:
			options.c_cflag &= ~CRTSCTS;	/* 默认无流控 */
			break;
	}

	/* control chars,1S first byte overtime & 1 byte received totally */	 
	options.c_cc[VTIME]= 10;	 
	options.c_cc[VMIN]= 1;

	/* local modes */
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

	/* 清空缓存 */
	tcflush(m_fd, TCIFLUSH);
	
	/* 设置属性 */
	if (tcsetattr (m_fd, TCSANOW, &options) != 0)	 
	{	       
		TRACE_ERR_CLASS("Can't set attribute!\n");	       
		return false;	 
	}	 
	
	/* 擦除m_fd的flags标志位(但不包括O_RDONLY,O_WRONLY,O_RDWR) */
	fcntl(m_fd, F_SETFL,0);
	return true;
}


/**
 *  \brief  关闭串口
 *  \param  none
 *  \return 成功返回true,失败返回false
 *  \note   none
 */
bool ComPort::close()
{
	return IODevice::close();
}

/**
 *  \brief  读串口数据
 *  \param  buf 存放读取的数据的缓存
 *  \param  len buf缓存的大小
 *  \param  timeoutMs 超时时间,-1:阻塞,0:立即返回,>0:超时时间ms
 *  \return 成功返回读取的数据长度,失败返回STATUS_ERROR
 *  \note   none
 */
int ComPort::readData(char * buf, int len, int timeoutMs)
{
	int ret;
	if (NULL==buf ||
		len<=0 ||
		m_fd < 0)
	{
		TRACE_ERR_CLASS("parameter error.\n");
		return STATUS_ERROR;
	}

	/* 设备自愈功能:出错后重新打开设备 */
	if(m_errorCount>20)
	{
		m_errorCount=0;
		close();
		open(m_devName.c_str(), m_openMode);
	}
	fd_set readfds;
	FD_ZERO(&readfds);
	FD_SET(m_fd, &readfds);

	struct timeval tv,*ptv;
    if(timeoutMs<0)
    {
        ptv=NULL;
    }
    else
    {
	    tv.tv_sec = timeoutMs/1000;
	    tv.tv_usec = (timeoutMs%1000)*1000;
        ptv = &tv;
    }
	ret=::select(m_fd+1, &readfds, NULL, NULL, ptv);
	if(0==ret)
	{
		m_errorCount=0;
	    //TRACE_ERR_CLASS("ERROR:read timeout.\n");
	    return STATUS_ERROR;
	}
	else if(ret<0)
	{
		TRACE_ERR_CLASS("select error:%s\n",ERROR_STRING);
		m_errorCount++;
        return STATUS_ERROR;
	}

	if (!FD_ISSET(m_fd, &readfds))
	{
		TRACE_ERR_CLASS("fd is not set.\n");
	    return STATUS_ERROR;
	}
	m_errorCount=0;
	return ::read(m_fd, buf, len);
}

/**
 *  \brief  写串口数据
 *  \param  data 要写入的数据
 *  \param  len 写入数据的长度
 *  \param  timeoutMs 超时时间,-1:阻塞,0:立即返回,>0:超时时间ms
 *  \return 成功返回写入的数据长度,失败返回STATUS_ERROR
 *  \note   none
 */
int ComPort::writeData(const char * buf, int len, int timeoutMs)
{
	int ret;
	if (NULL==buf ||
		len<=0 ||
		m_fd < 0)
	{
		TRACE_ERR_CLASS("parameter error.\n");
		return STATUS_ERROR;
	}

	#if 0	//just for test---zfg20141005
	TRACE_YELLOW("111111111--ComPort write-------------------\n");
	TRACE_HEX("com_write",(char*)buf, len);
	return len;
	#endif

	/* 设备自愈功能:出错后重新打开设备 */
	if(m_errorCount>20)
	{
		m_errorCount=0;
		close();
		open(m_devName.c_str(), m_openMode);
	}
	
	fd_set writefds;
	FD_ZERO(&writefds);
	FD_SET(m_fd, &writefds);
	
	struct timeval tv,*ptv;
    if(timeoutMs<0)
    {
        ptv=NULL;
    }
    else
    {
	    tv.tv_sec = timeoutMs/1000;
	    tv.tv_usec = (timeoutMs%1000)*1000;
        ptv = &tv;
    }
	ret=::select(m_fd+1, NULL, &writefds, NULL, ptv);
	if(0==ret)
	{
		m_errorCount=0;
	    //TRACE_ERR_CLASS("ERROR:write timeout.\n");
	    return STATUS_ERROR;
	}
	else if(ret<0)
	{
		TRACE_ERR_CLASS("select error:%s\n",ERROR_STRING);
		m_errorCount++;
        return STATUS_ERROR;
	}

	if (!FD_ISSET(m_fd, &writefds))
	{
		TRACE_ERR_CLASS("fd is not set.\n");
		return STATUS_ERROR;
	}
	m_errorCount=0;
	return ::write(m_fd, buf, len);
}

/**
 *  \brief  设置串口属性
 *  \param  attr 属性类型COM_ATTR_E
 *  \param  value 属性对应的值
 *  \return 成功返回STATUS_OK,失败返回STATUS_ERROR
 *  \note   none
 */
int ComPort::setAttribute(int attr, int value)
{
	if (m_fd >=0 )
	{
		TRACE_ERR_CLASS("Device has been opened,can't set attribute!\n");
		return STATUS_ERROR;
	}

	switch(attr)
	{
		case COM_ATTR_BAUDRATE:
			if (value != BAUD_1200 &&
				value != BAUD_2400 &&
				value != BAUD_4800 &&
				value != BAUD_9600 &&
				value != BAUD_19200 &&
				value != BAUD_38400 &&
				value != BAUD_57600 &&
				value != BAUD_115200 &&
				value != BAUD_460800)
			{
				TRACE_ERR_CLASS("Unsupport baud:%d.\n",value);
				return STATUS_ERROR;
			}
			m_attr.m_bandrate = (BAUDRATE_E)value;
			break;
		case COM_ATTR_DATABITS:
			if (value != DATA_5 &&
				value != DATA_6 &&
				value != DATA_7 &&
				value != DATA_8)
			{
				TRACE_ERR_CLASS("Unsupport databits:%d.\n",value);
				return STATUS_ERROR;
			}
			m_attr.m_databits = (DATABITS_E)value;
			break;
		case COM_ATTR_STOPBITS:
			if (value != STOP_1 &&
				value != STOP_2)
			{
				TRACE_ERR_CLASS("Unsupport stopbits:%d.\n",value);
				return STATUS_ERROR;
			}
			m_attr.m_stopbits = (STOPBITS_E)value;
			break;
		case COM_ATTR_PARITY:
			if (value != PARITY_NONE &&
				value != PARITY_ODD &&
				value != PARITY_EVEN &&
				value != PARITY_SPACE)
			{
				TRACE_ERR_CLASS("Unsupport parity:%d.\n",value);
				return STATUS_ERROR;
			}
			m_attr.m_parity = (PARITY_E)value;
			break;
		case COM_ATTR_FLOWCTRL:
			if (value != FLOWCTRL_SW &&
				value != FLOWCTRL_HW &&
				value != FLOWCTRL_NONE)
			{
				TRACE_ERR_CLASS("Unsupport flowctrl:%d.\n",value);
				return STATUS_ERROR;
			}
			m_attr.m_flowctrl = (FLOWCTRL_E)value;
			break;
		default:
			TRACE_ERR_CLASS("Unsupport attribute:%d.\n",attr);
			break;
	}	
	return STATUS_OK;
}

/**
 *  \brief  获取串口属性
 *  \param  attr 属性类型COM_ATTR_E
 *  \return 成功返回属性对应的值,失败返回STATUS_ERROR
 *  \note   none
 */
int ComPort::getAttribute(int attr)
{
	switch(attr)
	{
		case COM_ATTR_BAUDRATE:
			return m_attr.m_bandrate;
		case COM_ATTR_DATABITS:
			return m_attr.m_databits;
		case COM_ATTR_STOPBITS:
			return m_attr.m_stopbits;
		case COM_ATTR_PARITY:
			return m_attr.m_parity;
		case COM_ATTR_FLOWCTRL:
			return m_attr.m_flowctrl;
		default:
			return STATUS_ERROR;
	}
}